﻿using System;
using System.Data;

namespace LAPI.Data
{
    /// <summary>
    /// Clase para facilitar las acciones sobre base de datos.
    /// <p>Está configurada y optimizada para MySQL</p>
    /// </summary>
    public class GestorBBDD
    {        
        /// <summary>
        /// Conector MySQL
        /// </summary>
        private MySql.Data.MySqlClient.MySqlConnection _conexionBBDD;

        /// <summary>
        /// Logger de la clase
        /// </summary>
        private LAPI.Core.Logging.ILogger logger = LAPI.Core.Logging.LoggerFactory.CreateLogger(type: typeof(GestorBBDD));

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="cadenaConexion">Cadena de conexión a la base de datos MySQL</param>
        public GestorBBDD(String cadenaConexion)
        {
            _conexionBBDD = new MySql.Data.MySqlClient.MySqlConnection(connectionString: cadenaConexion);
        }

        #region Abrir/Cerrar BBDD
        /// <summary>
        /// Método para abrir una conexión a base de datos
        /// </summary>
        public void Abrir()
        {
            try
            {
                this._conexionBBDD.Open();
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.ConnectException(_conexionBBDD.ConnectionString, genEx);
            }
        }

        /// <summary>
        /// Método para destruir la conexión a base de datos
        /// <p>Se utiliza para cerrar la conexión cuando sabemos que no nos va a hacer falta abrir una conexión de nuevo, a continuación de este cierre.</p>
        /// </summary>
        public void Cerrar()
        {
            try
            {
                this._conexionBBDD.Close();
                this._conexionBBDD.Dispose();
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.ConnectException(_conexionBBDD.ConnectionString, genEx);
            }
        }

        /// <summary>
        /// Método para cerrar la conexión a base de datos
        /// <p>Se utiliza para cerrar la conexión cuando sabemos que vamos a hacer otra consulta a continuación</p>
        /// </summary>
        public void CerrarParcial()
        {
            try
            {
                this._conexionBBDD.Close();
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.ConnectException(_conexionBBDD.ConnectionString, genEx);
            }
        }

        /// <summary>
        /// Método para comprobar si la base de datos está o no activa
        /// </summary>
        /// <returns>true si está activa, y false si no lo está</returns>
        public  bool BDDActiva()
        {
            try
            {
                this._conexionBBDD.Open();
                this._conexionBBDD.Close();
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                return false;
            }
            return true;
        }
        #endregion

        #region Select/Insert/Delete/Update

        /// <summary>
        /// Método para realizar una consulta SELECT
        /// </summary>
        /// <param name="Query">String con la consulta a realizar</param>
        /// <returns>Dataset con los datos obtenidos en la consulta</returns>
        public DataSet Select(String Query)
        {
            MySql.Data.MySqlClient.MySqlCommand oComando;
            MySql.Data.MySqlClient.MySqlDataReader dsReader;

            try
            {
                oComando = new MySql.Data.MySqlClient.MySqlCommand(Query, this._conexionBBDD);
                dsReader = oComando.ExecuteReader();
                return this.ConvertirDataReaderEnDataSet(dsReader);
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: Query, excepcion: genEx);
            }
        }

        /// <summary>
        /// Método para realizar una consulta INSERT
        /// </summary>
        /// <param name="Query">String con la consulta a realizar</param>
        /// <returns>int con número de filas afectadas por la operación</returns>
        public Int32 Insert(String Query)
        {
            // Configuración del command
            MySql.Data.MySqlClient.MySqlCommand oComando;

            try
            {
                oComando = new MySql.Data.MySqlClient.MySqlCommand(Query, this._conexionBBDD);
                if (Query.Equals(string.Empty))
                    throw (new System.Exception());
                // Lanzamiento de la query
                return oComando.ExecuteNonQuery();
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: Query, excepcion: genEx);
            }            
        }

        /// <summary>
        /// Método para realizar una consulta UPDATE
        /// </summary>
        /// <param name="Query">String con la consulta a realizar</param>
        /// <returns>int con número de filas afectadas por la operación</returns>
        public Int32 Update(String Query)
        {
            // Configuración del command
            MySql.Data.MySqlClient.MySqlCommand oComando;

            try
            {
                oComando = new MySql.Data.MySqlClient.MySqlCommand(Query, this._conexionBBDD);
                if (Query.Equals(string.Empty))
                    throw (new System.Exception());
                // Lanzamiento de la query
                return oComando.ExecuteNonQuery();
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: Query, excepcion: genEx);
            }
        }

        /// <summary>
        /// Método para realizar una consulta DELETE
        /// </summary>
        /// <param name="Query">String con la consulta a realizar</param>
        /// <returns>int con número de filas afectadas por la operación</returns>
        public Int32 Delete(String Query)
        {
            // Configuración del command
            MySql.Data.MySqlClient.MySqlCommand oComando;

            try
            {
                oComando = new MySql.Data.MySqlClient.MySqlCommand(Query, this._conexionBBDD);
                if (Query.Equals(string.Empty))
                    throw (new System.Exception());
                // Lanzamiento de la query
                return oComando.ExecuteNonQuery();
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: Query, excepcion: genEx);
            }
        }

        /// <summary>
        /// Método para realizar una consulta SELECT que devuelva un solo valor entero.
        /// <p>Consulta optimizada para gran velocidad</p>
        /// </summary>
        /// <param name="Query">String con la consulta a realizar</param>
        /// <returns>int con el valor entero</returns>
        public Int32 QueryValorNumerico(String Query)
        {
            // Configuración del command
            MySql.Data.MySqlClient.MySqlCommand oComando;

            try
            {
                oComando = new MySql.Data.MySqlClient.MySqlCommand(Query, this._conexionBBDD);
                if (Query.Equals(string.Empty))
                    throw (new System.Exception());
                // Lanzamiento de la query
                return Convert.ToInt32(oComando.ExecuteScalar());
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: Query, excepcion: genEx);
            }
        }

        /// <summary>
        /// Método para realizar una consulta SELECT que devuelva un solo valor double.
        /// <p>Consulta optimizada para gran velocidad</p>
        /// </summary>
        /// <param name="Query">String con la consulta a realizar</param>
        /// <returns>double con el valor entero</returns>
        public Double QueryValorNumericoDouble(String Query)
        {
            // Configuración del command
            MySql.Data.MySqlClient.MySqlCommand oComando;

            try
            {
                oComando = new MySql.Data.MySqlClient.MySqlCommand(Query, this._conexionBBDD);
                if (Query.Equals(string.Empty))
                    throw (new System.Exception());
                // Lanzamiento de la query
                return Convert.ToDouble(oComando.ExecuteScalar());
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: Query, excepcion: genEx);
            }
        }

        /// <summary>
        /// Método para consultar cuál fue el último id generado.
        /// <p>Se utiliza tras un Insert a una tabla con clave primaria auto-incremental, para saber cuál es el identificador único</p>
        /// </summary>
        /// <returns>long con el identificador</returns>
        public long ObtenerIdGenerado()
        {
            // Configuración del command
            MySql.Data.MySqlClient.MySqlCommand oComando = new MySql.Data.MySqlClient.MySqlCommand("SELECT LAST_INSERT_ID()", this._conexionBBDD);

            try
            {
                return Convert.ToInt64(oComando.ExecuteScalar());
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: "SELECT LAST_INSERT_ID()", excepcion: genEx);
            }
        }

        /// <summary>
        /// Método para consultar cuál fue el último id generado.
        /// <p>Se utiliza tras un Insert a una tabla con clave primaria auto-incremental, para saber fué el último identificador único creado</p>
        /// </summary>
        /// <param name="idname">String con el nombre del campo que es auto-incremental</param>
        /// <param name="tablename">String con el nombre de la tabla que contiene el campo</param>
        /// <returns>long con el identificador</returns>
        public  long ObtenerIGenerado(String tablename, String idname)
        {
            // Configuración del command
            MySql.Data.MySqlClient.MySqlCommand oComando = new MySql.Data.MySqlClient.MySqlCommand("SELECT Max(" + idname + ") FROM " + tablename, this._conexionBBDD);

            try
            {
                return Convert.ToInt64(oComando.ExecuteScalar());
            }
            catch (System.Exception genEx)
            {
                logger.Error(message: genEx.Message, exception: genEx);
                throw new LAPI.Data.Exception.QueryException(consultaSQL: "SELECT Max(" + idname + ") FROM " + tablename, excepcion: genEx);               
            }
        }

        #endregion

        /// <summary>
        /// Método para convertir un DataReader en un DataSet.
        /// <p>Tiene como función optimizar el uso desconectado de los datos, ya que los DataReader han de estar
        /// abiertos para dejar iterar entre sus elementos
        /// </p>
        /// </summary>
        /// <param name="drEntrada">DataReader con los datos</param>
        /// <returns>Dataset con los datos</returns>
        protected  DataSet ConvertirDataReaderEnDataSet(MySql.Data.MySqlClient.MySqlDataReader drEntrada)
        {
            DataSet dsResult = new DataSet();
            DataTable tablaEsquema = drEntrada.GetSchemaTable();
            DataTable tablaDatos = new DataTable();
            int contador;

            // Si hay resultados, se construye un DataSet en base al DataReader
            if (tablaEsquema != null)
            {
                for (contador = 0; contador < tablaEsquema.Rows.Count; contador++)
                {
                    DataRow drFila = tablaEsquema.Rows[contador];
                    string nombreColumna = drFila["ColumnName"].ToString();
                    Type typeColumna = ((System.Type)(drFila.ItemArray[11]));
                    DataColumn dcColumna = new DataColumn(nombreColumna, typeColumna);
                    //DataColumn dcColumna = new DataColumn(nombreColumna, ((System.RuntimeType)(drFila["DataType"])));

                    tablaDatos.Columns.Add(dcColumna);
                }
                dsResult.Tables.Add(tablaDatos);
                while (drEntrada.Read())
                {
                    DataRow drFila = tablaDatos.NewRow();
                    for (contador = 0; contador < drEntrada.FieldCount; contador++)
                        drFila[contador] = drEntrada.GetValue(contador);
                    tablaDatos.Rows.Add(drFila);
                }

            }
            else	// No se devolvieron resultados, se construye un DataSet vacío
            {
                DataColumn dcColumna = new DataColumn("RowsAffected");
                tablaDatos.Columns.Add(dcColumna);
                dsResult.Tables.Add(tablaDatos);
                DataRow drFila = tablaDatos.NewRow();
                drFila[0] = drEntrada.RecordsAffected;
                tablaDatos.Rows.Add(drFila);
            }

            return dsResult;
        }
    }
}